﻿Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Reflection

Namespace Dundas.Gauges.WebControl
    ''' <summary>
    ''' Control adapter which lets the Dundas Gauge control be used with an HTTP handler
    ''' </summary>
    Public Class GaugeHttpHandlerAdapter
        Inherits System.Web.UI.Adapters.ControlAdapter
        ''' <summary>
        ''' Render the gauge.  We prevent the gauge's render method from being called as this
        ''' will cause a temp image to be written (which is what we're trying to avoid with the HTTP handler).
        ''' </summary>
        ''' <param name="writer"></param>
        Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter)
            ' Render the gauge to an image
            Dim gauge As GaugeContainer = DirectCast(Me.Control, GaugeContainer)
            Dim ms As New System.IO.MemoryStream()
            gauge.SaveAsImage(ms, GaugeImageFormat.Png)

            Dim imageData As Byte() = ms.ToArray()

            ' Create an image control for the gauge image
            Dim img As New Image()
            img.ID = Me.Control.UniqueID
            img.Height = gauge.Height
            img.Width = gauge.Width

            Me.CopyAttributes(gauge, img)
            img.Attributes("onerror") = "this.alt = 'Gauge image unavailable'"

            ' Save the image to the cache, get its ID, and create the URL to the HTTP handler
            Dim imageID As UInteger = GaugeHttpHandler.AddGaugeImage(imageData)
            img.ImageUrl = Me.CreateImageUrl(imageID)

            ' Render the gauge's map areas
            Me.RenderMapAreas(gauge, img, writer)
        End Sub

        ''' <summary>
        ''' Copy all of the gauge's attributes onto our replacement image control
        ''' </summary>
        ''' <param name="gauge"></param>
        ''' <param name="target"></param>
        Private Sub CopyAttributes(ByVal gauge As GaugeContainer, ByVal target As Image)
            For Each key As String In gauge.Attributes.Keys
                Dim value As String = gauge.Attributes(key)
                target.Attributes(key) = value
            Next
        End Sub

        ''' <summary>
        ''' Create the URL to the gauge's HTTP handler
        ''' </summary>
        ''' <param name="imageID"></param>
        ''' <returns></returns>
        Private Function CreateImageUrl(ByVal imageID As UInteger) As String
            Dim coll As NameValueCollection = HttpUtility.ParseQueryString("")
            coll.Add("ImageID", imageID.ToString())
            Return String.Format("{0}?{1}", Me.Control.ResolveUrl("~/GaugeHandler.axd"), coll.ToString())
        End Function

        ''' <summary>
        ''' Render the gauge's map areas
        ''' </summary>
        ''' <param name="gauge"></param>
        ''' <param name="gaugeImage"></param>
        ''' <param name="writer"></param>
        Private Sub RenderMapAreas(ByVal gauge As GaugeContainer, ByVal gaugeImage As Image, ByVal writer As HtmlTextWriter)
            Dim core As GaugeCore = GetFieldValue(Of GaugeCore)(gauge, GetType(GaugeCore), "a")

            ' Just render the image if map areas are disabled
            If core.MapEnabled = False Then
                gaugeImage.RenderControl(writer)
                Exit Sub
            End If

            ' Setup the image map
            Dim mapName As String = String.Format("{0}ImageMap", gauge.UniqueID)
            gaugeImage.Attributes.Add("USEMAP", String.Format("#{0}", mapName))

            ' Let the gauge core instance populate itself with map areas
            Dim e As MethodInfo = GetMethod(GetType(GaugeCore), "e")
            e.Invoke(core, Nothing)

            ' Render the img tag first
            gaugeImage.RenderControl(writer)

            ' Let the gauge render the map areas
            Dim a As MethodInfo = GetMethod(GetType(GaugeCore), "a", GetType(System.IO.TextWriter), GetType(String))
            a.Invoke(core, New Object() {writer, mapName})
        End Sub

        ''' <summary>
        ''' Gets a field of a given type and with a given name
        ''' </summary>
        ''' <param name="t"></param>
        ''' <param name="fieldType"></param>
        ''' <param name="name"></param>
        ''' <returns></returns>
        Private Shared Function GetField(ByVal t As Type, ByVal fieldType As Type, ByVal name As String) As FieldInfo
            Dim data As Object() = {fieldType, name}
            Dim arr As MemberInfo() = t.FindMembers(MemberTypes.Field, BindingFlags.Instance Or BindingFlags.NonPublic, AddressOf FieldFilter, data)
            Return CType(arr(0), FieldInfo)
        End Function

        ''' <summary>
        ''' Get the strongly-typed value of a field
        ''' </summary>
        ''' <typeparam name="T"></typeparam>
        ''' <param name="instance"></param>
        ''' <param name="fieldType"></param>
        ''' <param name="fieldName"></param>
        ''' <returns></returns>
        Private Shared Function GetFieldValue(Of T)(ByVal instance As Object, ByVal fieldType As Type, ByVal fieldName As String) As T
            Dim fi As FieldInfo = GetField(instance.[GetType](), fieldType, fieldName)
            Return CType(fi.GetValue(instance), T)
        End Function

        ''' <summary>
        ''' Keep only fields with a given field type and name
        ''' </summary>
        ''' <param name="m"></param>
        ''' <param name="filterCriteria"></param>
        ''' <returns></returns>
        Private Shared Function FieldFilter(ByVal m As MemberInfo, ByVal filterCriteria As Object) As Boolean
            Dim data As Object() = DirectCast(filterCriteria, Object())
            Dim fieldType As Type = DirectCast(data(0), Type)
            Dim name As String = DirectCast(data(1), String)
            Dim fi As FieldInfo = DirectCast(m, FieldInfo)
            Return fi.FieldType.Equals(fieldType) AndAlso fi.Name.Equals(name, StringComparison.Ordinal)
        End Function

        ''' <summary>
        ''' Get a method on a type with a given set of parameters
        ''' </summary>
        ''' <param name="t"></param>
        ''' <param name="name"></param>
        ''' <param name="parameterTypes"></param>
        ''' <returns></returns>
        Private Shared Function GetMethod(ByVal t As Type, ByVal name As String, ByVal ParamArray parameterTypes As Type()) As MethodInfo
            If parameterTypes Is Nothing Then
                parameterTypes = New Type() {}
            End If

            Return t.GetMethod(name, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, parameterTypes, Nothing)
        End Function
    End Class
End Namespace
